package saves;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import game.Configurations;
import game.Data;
import java.io.*;
import java.util.ArrayList;
import math.Point;
import static org.lwjgl.opengl.GL11.*;
import org.newdawn.slick.Color;
import units.Time;

public class CorpoOrbitavel extends CorpoCeleste {

    private static final long serialVersionUID = -3332057514775220707L;
    private int tipo; //0-Telúrico, 1-Gasoso
    private float pressaoMedia, temperaturaAtual; //Em kPa e Cº
    private double[] gasesAtmosfericos; //Concentração de cada gas atmosférico
    private float velAngRot, anguloEixoTranslacao, angRot, angTrans; //Em pi*rad e 10^-5 pi*rad/s
    private float rh1, rh2;
    private CorpoCeleste[] pai;
    private CorpoOrbitavel[] filhos;
    private int codArea;
    private long lastUpdate;
    //Partes mutáveis
    private transient Info info;
    private static ArrayList<Point.Inteiro> pts = new ArrayList<>();

    @SuppressWarnings("unused")
    private CorpoOrbitavel() {
    }

    public CorpoOrbitavel(int codArea, String nome, float massa, float raio, int tipo, float pressaoMedia, double[] gasesAtmosfericos, float velAngRot, float anguloEixoTranslacao, float rh1, float rh2, float angRot, float angTrans, CorpoCeleste[] pai, CorpoOrbitavel[] filhos, Info info) {
        super(nome, massa, raio);
        this.tipo = tipo;
        this.pressaoMedia = pressaoMedia;
        this.gasesAtmosfericos = gasesAtmosfericos;
        this.velAngRot = velAngRot;
        this.anguloEixoTranslacao = anguloEixoTranslacao;
        this.rh1 = rh1;
        this.rh2 = rh2;
        this.angRot = angRot;
        this.angTrans = angTrans;
        this.pai = pai;
        this.filhos = filhos;
        this.codArea = codArea;
        this.lastUpdate = -1;

        float lumPai = 0;
        for (int i = 0; i < pai.length; i++)
            lumPai += pai[i].getLuminosidade();
        this.temperaturaAtual = Data.gerarCoefCalorAtm(gasesAtmosfericos) * rh1 * lumPai;

        this.info = info;
    }

    @Override
    public float getLuminosidade() {
        return this.temperaturaAtual;
    }

    public double getMassaPai() {
        double mass = 0;
        for (int i = 0; i < pai.length; i++)
            mass += pai[i].getMassa();
        return mass;
    }

    public double getTheta(Time tempo) {
        //Informa��es da �rbita
        double a = (rh2 + rh1) / 2, e = (rh2 - rh1) / (rh2 + rh1), b = a * Math.sqrt(1 - e * e), a3 = Math.pow(a, 3);

        //Calcular per�odo total
        double ia = a / 3.240755744, ib = getMassaPai(), ic = getMassa();
        //C = 10^(7*3 + -9) = 112
        double pt = Math.sqrt(4 * Math.PI * Math.PI * Math.pow(ia, 3) * Math.pow(10, 12) / ((6.67428) * (ib + ic)));
        double at = Math.PI * a * b;

        //Calcular a �rea percorrida at� agora desde o �ltimo ciclo
        double st = tempo.getSegundosTotais() % pt;
        double av = st * at / pt;

        //av = 0;

        double aa = Math.atan(b * Math.tan(0 * Math.PI * angTrans) / a), alpha = Math.atan((a * Math.tan((a3 * aa + 2 * av * b) / a3)) / b);

        //Corre��o para arctan de tans maiores que 90
        av += a3 * aa / (2 * b);
        if (av > at / 4 && (av > at / 2 || alpha < 0.25 * Math.PI) && (av < 3 * at / 4 || alpha > 0))
            alpha += Math.PI;
        alpha += 2 * Math.PI;
        alpha %= 2 * Math.PI;

        //System.out.println("PT: " + (long) pt + " | ST: " + st + " | Alpha: " + alpha);

        return alpha;
    }
    
    public Point getPosicao(double theta) {
        double a = (rh2 + rh1) / 2, e = (rh2 - rh1) / (rh2 + rh1), b = a * Math.sqrt(1 - e * e), c = (rh2 - rh1) / 2;
        double a2 = Math.pow(a, 2), b2 = Math.pow(b, 2), c2 = Math.pow(c, 2);

        double t = Math.tan(theta), t2 = Math.pow(t, 2), at = a2 * t2;
        double xo = (Math.sqrt(a2 * b2 * (at + b2 - c2 * t2)) - b2 * c) / (at + b2), yo = t * xo;

        if (theta < 3 * Math.PI / 2 && theta >= Math.PI / 2) {
            xo *= -1;
            yo *= -1;
        }
        
        return new Point((float) xo, (float) yo);
    }
    
    public Point getPosicao(Time t) {
        return getPosicao(getTheta(t));
    }

    public void desenhar(Point p, double zoom) {
        float xo = p.x, yo = p.y;

        int x = (int) (xo * zoom / 1000) + Configurations.SCREEN_WIDTH / 2, y = (int) (yo * zoom / 1000) + Configurations.SCREEN_HEIGHT / 2, cx = (int) ((rh2 - rh1) * zoom / (2 * 1000)) + Configurations.SCREEN_WIDTH / 2, cy = Configurations.SCREEN_HEIGHT / 2;
        //x and y in 10^-10 parsecs from the center

        pts.add(new Point.Inteiro(x, y));
        Color.pink.bind();
        drawSquare(cx, cy, 1);

        Color.white.bind();
        for (int i = 0; i < pts.size(); i++)
            drawSquare(pts.get(i).x, pts.get(i).y, 1);

        Color.green.bind();
        drawSquare(x, y, 5);
    }

    private void drawSquare(int x, int y, int s) {
        glBegin(GL_QUADS);
        {
            glVertex2i(x - s, y - s);
            glVertex2i(x + s, y - s);
            glVertex2i(x + s, y + s);
            glVertex2i(x - s, y + s);
        }
        glEnd();
    }

    public void restartState() {
        this.info = null;
    }

    public void loadInfo(String path) {
        try (Input in = new Input(new FileInputStream(new File(path + codArea + ".info")))) {
            info = new Kryo().readObject(in, Info.class);
        } catch (IOException e) {
            System.err.println("Houve um erro na leitura do arquivo '" + path + codArea + ".info" + "'. Erro: " + e.getMessage());
        }
    }

    public void saveInfo(String path) {
        try (Output out = new Output(new FileOutputStream(new File(path + codArea + ".info")))) {
            new Kryo().writeObject(out, info);
        } catch (IOException e) {
            System.err.println("Houve um erro na escrita do arquivo '" + path + codArea + ".info" + "'. Erro: " + e.getMessage());
        }
    }

    public boolean isCompleteInfo() {
        return (this.info != null);
    }

    public Info getCorpoInfo() {
        return this.info;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public float getAnguloEixoTranslacao() {
        return anguloEixoTranslacao;
    }

    public void setAnguloEixoTranslacao(float anguloEixoTranslacao) {
        this.anguloEixoTranslacao = anguloEixoTranslacao;
    }

    public float getMassa() {
        return massa;
    }

    public void setMassa(float massa) {
        this.massa = massa;
    }

    public float getRaio() {
        return raio;
    }

    public void setRaio(float raio) {
        this.raio = raio;
    }

    public int getCodArea() {
        return codArea;
    }

    public CorpoOrbitavel[] getFilhos() {
        return filhos;
    }

    public void setFilhos(CorpoOrbitavel[] filhos) {
        this.filhos = filhos;
    }

    public float getPressaoMedia() {
        return pressaoMedia;
    }

    public void setPressaoMedia(float pressaoMedia) {
        this.pressaoMedia = pressaoMedia;
    }

    public int getTipo() {
        return tipo;
    }

    public double[] getGasesAtmosfericos() {
        return gasesAtmosfericos;
    }

    public float getAngRot() {
        return angRot;
    }

    public static class Info implements Serializable {

        private static final long serialVersionUID = -2820264551560223491L;
        private Faccao faccaoControle;
        private int lifeState, attackState;

        public Info() {
            this.faccaoControle = null;
            this.lifeState = 0;
            this.attackState = 0;
        }

        public Info(Faccao faccaoControle, int lifeState, int attackState) {
            this.faccaoControle = faccaoControle;
            this.lifeState = lifeState;
            this.attackState = attackState;
        }

        public Faccao getFaccaoControle() {
            return faccaoControle;
        }

        public void setFaccaoControle(Faccao faccaoControle) {
            this.faccaoControle = faccaoControle;
        }

        public int getLifeState() {
            return lifeState;
        }

        public void setLifeState(int lifeState) {
            this.lifeState = lifeState;
        }

        public int getAttackState() {
            return attackState;
        }

        public void setAttackState(int attackState) {
            this.attackState = attackState;
        }
    }
}
